﻿#region namespaces

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using ZOIL.DataBackend.BaseX.XQueries;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    public sealed partial class Database
    {
        /// <summary>
        ///     Fetches the pair with the passed id
        ///     Returns local existing object if already fetched
        /// </summary>
        /// <param name="id"> </param>
        /// <returns> </returns>
        public Tuple<IPersistable, IVisualProperties> Load(long id)
        {
            if (id.IsDefault())
            {
                throw new ArgumentException("No valid id!");
            }
            //erst im manager schaun ob schon lokal vorhanden
            DatabaseObjectManager.DbItem dbi;
            if (_localDbObjects.TryGet(id, out dbi))
            {
                return dbi.ToTuple();
            }

            //sonst aus db holen
            var objProp =
                _client.Query(XQueriesLoader.GetLoadObjectPairByIdQuery(id, DbConfig.DatabaseIdInIDatabaseObject,
                                                                        DbConfig.DatabaseIdInIVisualProperties)).ToArray();

            if (objProp.Length <= 0)
            {
                throw new ArgumentException(string.Format("No element with DatabaseId {0} in database {1}", id, DbConfig.DatabaseName), "id");
            }
            if (objProp.Length != 2)
            {
                throw new ArgumentException(
                    string.Format(
                        "Too much elements with DatabaseId {0} in database {1}. Should be 2 (1 IDatabaseObject, 1 IVisualProperties)  but is {2}", id,
                        DbConfig.DatabaseName, objProp.Length), "id");
            }

            var objXml = XElement.Parse(objProp[0]);
            var propXml = XElement.Parse(objProp[1]);

            //object holen
            var obj = (IPersistable) SerializationHelper.Deserialize(objXml, DbConfig);
            //prop holen
            var prop = (IVisualProperties) SerializationHelper.Deserialize(propXml, DbConfig);

            //und in manager packen
            _localDbObjects.Store(ref obj, ref prop);

            return Tuple.Create(obj, prop);
        }

        public List<Tuple<IPersistable, IVisualProperties>> Load(string xquery)
        {
            if (string.IsNullOrWhiteSpace(xquery))
            {
                throw new ArgumentException("No XQuery specified!", "xquery");
            }

            var query = XQueriesLoader.GetLoadByXQueryQuery(DbConfig.DatabaseIdInIDatabaseObject, DbConfig.DatabaseIdInIVisualProperties) + xquery;
            //TODO: Wenn fehlerhafter Query dann auch noch in InvalidXQueryException packen
            var res = _client.Query(query, DbConfig.DatabaseName).Select(XElement.Parse).ToList();

            if (res.Count%2 != 0)
            {
                throw new InvalidXQueryException(
                    "Number of results is odd (instead of even: in each case one IDatabaseObject and one IVisualProperties", xquery, query, res);
            }

            var results = new List<Tuple<IPersistable, IVisualProperties>>(res.Count/2);
            for (int i = 0; i < res.Count;)
            {
                try
                {
                    var obj = (IPersistable) SerializationHelper.Deserialize(res[i++], DbConfig);

                    var id = obj.DatabaseId;
                    DatabaseObjectManager.DbItem dbi;

                    if (!_localDbObjects.TryGet(id, out dbi))
                    {
                        //sonst die hier verwenden und die neuen eintragen
                        var prop = (IVisualProperties) SerializationHelper.Deserialize(res[i++], DbConfig);
                        dbi = _localDbObjects.Store(obj, prop);

                        //und auf änderungen hören
                        ListenToChangesIfPossible(obj, prop);
                    }
                    results.Add(dbi.ToTuple());
                }
                catch (InvalidCastException e)
                {
                    throw new InvalidXQueryException(
                        "Result was not in the correct format: Must always be pairs starting with IDatabaseObject followed by the matching IVisualProperties; ",
                        xquery, query, res, e);
                }
            }

            return results;
        }

        public IPersistable GetPersistable(long id)
        {
            return Load(id).Item1;
        }

        public IVisualProperties GetVisualProperties(long id)
        {
            return Load(id).Item2;
        }

        public Tuple<IPersistable, IVisualProperties> GetPersistableAndVisualProperties(long id)
        {
            return Load(id);
        }
    }
}